iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 4
0

禮拜四的凌晨,台北的天空霧濛濛的,彷彿預告著明天的壞天氣還有等不到週末的壞心情。

不過珍妮可不在意這些,今天是她重要的日子。

珍妮要灑錢打造屬於自己的一輛車,順手振興台灣經濟。她的心裡在考慮兩種車:轎車和跑車。但是珍妮自己不太懂車呀,於是她委託懂車的工頭/工人來負責製造。這個工頭告訴珍妮:轎車的零件有車體、引擎,而跑車的零件有車體、引擎、酷炫擾流板。而他旁邊這個工人很萬能,會安裝三樣零件:車體、引擎、酷炫擾流板。所以如果珍妮委託工頭/工人做一台轎車,工頭就會請工人安裝車體、安裝引擎;如果珍妮委託工頭/工人做一台跑車,工頭就會請工人安裝車體、安裝引擎、安裝酷炫擾流板。

這個例子中的工人就是 Builder,工頭則是 Director,而那些被製造的轎車和跑車稱作 Product。

比較正式點說,Builder 是一種創建型設計模式:任何複雜 object (稱作 product) 的創建過程都只是請工人安裝一連串的零件而已。

Builder 專門用在需要很多零件才能創建的複雜 product:把各種 product 的構成拆解成多個零件,builder 就像是工人,只負責執行安裝各個零件,但不知道哪些零件串在一起會做出什麼 product;而 director 就像是工頭,自己不會安裝任何零件,但負責記住哪幾個零件裝在一起可以做出什麼 product。所以當要創建某個 product,director 就會請 builder 安裝該 product 需要的所有零件。

不用吧,明明就有兩個比較簡單的設計方法呀

  1. 兩個建構子(Constructor)各自負責轎車、跑車就好啦?
    當有幾十種車時,就會有幾十種建構子,每個建構子裡面都有很類似的「安裝引擎」的功能,就造成「安裝引擎」的功能被重複寫了幾十次,以後每次改都要改幾十次。比較麻煩。

  2. 只要一個建構子負責接收 3 個參數:車體種類、引擎種類、擾流板種類,再根據接收的參數製造不同車種就好啦?
    當要製造的車種類繁多時(包含計程車 垃圾車 救護車 靈車 閃電霹靂車...),假設總共有 10 個參數,大部分的轎車只要 2 個參數就能製造。那呼叫那個大建構子來製造大部分的車的時候,輸入參數的部分常常有 8 個欄位都是空的。比較醜。

此外的好處是,builder 同時也符合單一功能原則:複雜的安裝XX功能都被獨立於 builder 裡面了。

而壞處則來自於我們創造的額外 classes,以這種角度來看程式碼是變複雜了。

來張圖吧

03_builder

寫成程式碼吧

from abc import ABC

# 汽車
class Car():
    def __init__(self):
        self.parts = "A car with "

    def add(self, part):
        self.parts += (part + " ")


# 機車
class Motorcycle():
    def __init__(self):
        self.parts = "A motorcycle with "

    def add(self, part):
        self.parts += (part + " ")


# 抽象的builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class Builder(ABC):

    def get_product(self):
        pass

    def install_body(self):
        pass

    def install_engine(self):
        pass

    def install_spoiler(self):
        pass

# 汽車builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class CarBuilder(Builder):

    # 初始化:產生空的汽車供安裝各種部件
    def __init__(self):
        self.reset()
    
    # 產生空的汽車供安裝各種部件
    def reset(self):
        self._product = Car()

    # [細節] 通常每個builder要負責提供方法獲取他製造的東西,因為不同builder可能回傳完全不同class的object,比如說CarBuilder製造Car,而MotorcycleBuilder製造Motorcycle
    # 通常builder回傳製造的東西以後要重設,準備下一輪的安裝,所以通常會呼叫reset()
    def get_product(self):
        product = self._product
        self.reset()
        return product

    def install_body(self):
        self._product.add("car_body")

    def install_engine(self):
        self._product.add("car_engine")

    def install_spoiler(self):
        self._product.add("car_spoiler")

# 機車builder會 (0)回傳product (1)安裝車體 (2)安裝引擎 (3)安裝擾流板
class MotorcycleBuilder(Builder):

    # 初始化:產生空的機車供安裝各種部件
    def __init__(self):
        self.reset()
    
    # 產生空的機車供安裝各種部件
    def reset(self):
        self._product = Motorcycle()

    def get_product(self):
        product = self._product
        self.reset()
        return product

    def install_body(self):
        self._product.add("motorcycle_body")

    def install_engine(self):
        self._product.add("motorcycle_engine")

    def install_spoiler(self):
        self._product.add("motorcycle_spoiler")

# Director負責記得製造哪種車需要哪些零件,然後會呼叫builder負責安裝每個零件
class Director():

    def __init__(self):
        self._builder = None

    # 用來存取builder,因為我們要負責指派builder給director
    def builder(self):
        return self._builder

    # 普通車款要安裝車體、安裝引擎
    def build_basic_entity(self):
        self.builder.install_body()
        self.builder.install_engine()

    # 運動型車款要安裝車體、安裝引擎、安裝擾流板
    def build_sports_entity(self):
        self.builder.install_body()
        self.builder.install_engine()
        self.builder.install_spoiler()

if __name__ == "__main__":

    # 假設現在要製造轎車跟跑車(兩種汽車)
    director = Director()
    car_builder = CarBuilder()
    director.builder = car_builder

    print("Constructing basic car")
    director.build_basic_entity()
    print("product completed:", car_builder.get_product().parts)
    
    print("\n")

    print("Constructing sports car")
    director.build_sports_entity()
    print("product completed:", car_builder.get_product().parts)

    print("\n")

    # [細節] 如果要客製化某台很特別的車(只有車體跟擾流板的展示用車),其實我們也可以不用director直接呼叫car_builder內部的安裝功能
    print("Constructing custom car (without director)")
    car_builder.install_body()
    car_builder.install_spoiler()
    print("product completed:", car_builder.get_product().parts)

    print("\n")

    # [細節] director記得運動型車款的零件菜單:如果交給他CarBuilder會做出四輪跑車,如果交給他MotorcycleBuilder會做出兩輪跑車
    motorcycle_builder = MotorcycleBuilder()
    director.builder = motorcycle_builder
    print("Constructing sports motorcycle")
    director.build_sports_entity()
    print("product completed:", motorcycle_builder.get_product().parts)

所以我說,那個珍妮呢?

在工頭/工人的幫助下,熱愛速度感的珍妮終於完成了她人生中第一輛跑車,也或多或少地振興了台灣經濟,可喜可賀。

更可喜可賀的是,你也堅持著看完今天的文章了。除了感謝閱讀,也希望你從今天的文章獲得一些想法,明天繼續一起學習 design pattern 吧!

那麼,明天就輪到 composite 登場啦!敬請期待!

Reference

  1. Dive Into Design Patterns by Alexander Shvets
  2. https://refactoring.guru

作者:Allen


上一篇
[Design Pattern] Factory Method 工廠方法
下一篇
[Design Pattern] Composite 組合模式
系列文
什麼?又是/不只是 Design Patterns!?32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言